<?php

class PHPSandBox {
  var $source, $allowedCalls = array();
  
  function __construct($scriptText) {
    $this->source = $scriptText;
  }

  function _allowHarmlessCalls() {
    $this->allowedCalls = array_merge($this->allowedCalls, explode(',', 
      'explode,implode,date,time,round,trunc,rand,ceil,floor,srand,'.
      'strtolower,strtoupper,substr,stristr,strpos,print,print_r,return'.
			',getDirListing,nd,scrapeURL,parse_costs,parse_accred,true,false'));
  }
  
  function parse() {
    $this->parseErrors = array();
    $tokens = token_get_all('<?'.'php '.$this->source.' ?'.'>');    
    $vcall = '';
    
    foreach ($tokens as $token) {
      if (is_array($token)) {
        $id = $token[0];
        switch ($id) {
          case(T_VARIABLE): { $vcall .= 'v'; break; }
          case(T_STRING): { $vcall .= 's'; }
          case(T_REQUIRE_ONCE): case(T_REQUIRE): case(T_NEW): case(T_RETURN): 
          case(T_BREAK): case(T_CATCH): case(T_CLONE): case(T_EXIT): 
          case(T_PRINT): case(T_GLOBAL): case(T_ECHO): case(T_INCLUDE_ONCE): 
          case(T_INCLUDE): case(T_EVAL): case(T_FUNCTION): { 
            if (array_search($token[1], $this->allowedCalls) === false)
              $this->parseErrors[] = 'illegal call: '.$token[1].' of type '.$id;
          }            
        }
      }     
      else 
        $vcall .= $token;
    }
    
    if (stristr($vcall, 'v(') != '') 
      $this->parseErrors[] = array('illegal dynamic function call');
    
    if (sizeof($this->parseErrors) > 0)
      $this->parseErrors[] = ' vcall='.$vcall;
      
    return($this->parseErrors);
  }

}

class ProcessBox extends PHPSandBox {
	var $nd; // from node data 

	function __construct($code_str, $node_data) {
		parent::__construct($this->_toPHPStatmentString($code_str));
		$this->_allowHarmlessCalls();
		$this->parse();
		$this->nd = $node_data;
	}
	
	/**
	 * internal syntax to transform from nth-shorthand to PHP
	 * example: ~result = ~a + ~b is transformed into
	 * $nd->result = $nd->a + $nd->b;
	 * @param string $statement node process in shorthand form
	 * @return string PHP code that is ready to be evaluated.	 
	 * A side effect of runing this translator is that it adds 
	 * any node data (nd) references on to the allowedCalls array.
	 */	 	
	function _toPHPStatmentString($statement) {
		$st = str_replace('~', '$nd->', $statement);
		$st .= "; return \$nd;";
		preg_match_all('/nd->(?P<name>\w+)/', $st, $matches);
		foreach ($matches['name'] as $k => $v) {
          $this->allowedCalls[] = $v;
        }
		return $st;
	}	
	// From object to array.
	function toArray($data) {
	    if (is_object($data)) $data = get_object_vars($data);
	    return is_array($data) ? array_map(NULL, $data) : $data;
	}
	// From array to object.
	function conv_obj($Data){
	    if(!is_object($Data) && !is_array($Data)) return $Data;
	    if(is_object($Data)) $Data = get_object_vars($Data);
	    return array_map(NULL, $Data);
	}
    function execute() {
        //$nd = $this->conv_obj($this->nd);
    	foreach ($this->nd as $k => $v) {
            $nd->$k = $v;
        }
        if (sizeof($this->parseErrors) == 0) {
            return $this->toArray(eval($this->source));
            //return eval($this->source);
        }
        else {
            print('<br/>class::PHPSandBox error: cannot execute, script (private) contains errors');
            print('<br/>class::PHPSandBox parseErrors:');
            print_r($this->parseErrors);
    	}
    } 
}

class GuardBox extends PHPSandBox {
	var $nd; // from node data 

	function __construct($code_str, $node_data) {
		parent::__construct($this->_toPHPStatmentString($code_str));
		$this->_allowHarmlessCalls();
		$this->parse();
		$this->nd = $node_data;
	}
	
	/**
	 * internal syntax to transform from nth-shorthand to PHP
	 * example: ~result = ~a + ~b is transformed into
	 * $nd->result = $nd->a + $nd->b;
	 * @param string $statement node process in shorthand form
	 * @return string PHP code that is ready to be evaluated.	 
	 * A side effect of runing this translator is that it adds 
	 * any node data (nd) references on to the allowedCalls array.
	 */	 	
	function _toPHPStatmentString($statement) {
		$st = "return (BOOL)(".str_replace('~', '$nd->', $statement);
		$st .= ");";
		preg_match_all('/nd->(?P<name>\w+)/', $st, $matches);
		foreach ($matches['name'] as $k => $v) {
          $this->allowedCalls[] = $v;
        }
		return $st;
	}	
	// From object to array.
	function toArray($data) {
	    if (is_object($data)) $data = get_object_vars($data);
	    return is_array($data) ? array_map(NULL, $data) : $data;
	}
	// From array to object.
	function conv_obj($Data){
	    if(!is_object($Data) && !is_array($Data)) return $Data;
	    if(is_object($Data)) $Data = get_object_vars($Data);
	    return array_map(NULL, $Data);
	}
    function eval_guard() {
        //$nd = $this->conv_obj($this->nd);
    	foreach ($this->nd as $k => $v) {
            $nd->$k = $v;
        }
        if (sizeof($this->parseErrors) == 0) {
            return (BOOL) eval($this->source);
            //return eval($this->source);
        }
        else {
            print('<br/>class::PHPSandBox error: cannot execute, script "'.$this->source.'" contains errors');
            print('<br/>class::PHPSandBox parseErrors:');
            print_r($this->parseErrors);
    	}
    } 
}

